#
# 2020.09.13    Hiroshi    set DS_FT_FLAG to FT if file name has _QA_  (*1)
# 2020.09.18    Alien      Added Limit Rule 5
# 2020.09.25    Tam        1. Fix a bug:  When '-outDir ./' and running script under the same fold of datafile,
#                                         the final outfile will be delete
#                          2. Remove useless code, and optimize some codes.
#                          3. Fix a bug:  Limit file may have 125 TESTS or 250 TESTS.
#                                         Old script can't deal the case with 125 TESTS.
#

import struct, re
import os, sys, time
import numpy as np
import shutil
import gzip
import io
from TOOLS import *


RES_FIL = sys.argv[1]  # res file
LIM_FIL = sys.argv[2]  # limit file
BIN_MAP = sys.argv[3]  # bin mapping file
OUT_DIR = sys.argv[4]  # output directory
DEBUG = sys.argv[6]
#with open("debug_add_test_lim", "w") as dbf:
#    dbf.write("sys.argv is {}\n".format(sys.argv))
GLB = {}
LIM_OUT = LIM_FIL + '.out.csv'  # parsed limit file
FIN_OUT = RES_FIL + '.addLim.csv'  # final output file
DS_FT_FLAG = 'FT' if re.findall(r'(_(FT|QA)_)', RES_FIL) else 'DS'  # FT file or DS file #(*1)

type_table = ['s', 'c', 'b', 'B', 'h', 'H', 'i', 'I', 'l', 'L', 'q', 'Q', 'e', 'f', 'd']
len_table = [1, 1, 1, 1, 2, 2, 4, 4, 4, 4, 8, 8, 2, 4, 8]
#############################################################################################################


# BIN_MAP
BIN_MAP_LIST = []
with open(BIN_MAP) as BIN:
    BIN.readline()
    for line in BIN:
        line = line.strip()
        BIN_MAP_LIST.append(line.split(','))

def output(name, unpacked, data, d_binary=False, bin_len=1):
    data1 = data.hex()
    data2 = bin(int(data1, 16))[2:].rjust(8 * bin_len, "0")
    if d_binary:
        unpacked = data2
    W.write(f'{name},{unpacked},{data1},{data2}\n')

def b2int(data):
    return int(data.hex(), 16)

def find_enabled_by_test_num(test_num):
    for i in BIN_MAP_LIST:
        test_num_group = i[2].split('/')
        test_num_group = [int(i) for i in test_num_group]
        if test_num in test_num_group:
            return int(i[0])
    exit_for_loader(1, f'can not find enabled_value by test number {test_num}\n')


# def type class
def CreateBasicClass(tp_name):
    class Basic:
        def __init__(self, name):
            self.name = name
            self.tp_name = tp_name
            indx = type_table.index(tp_name)
            self.length = len_table[indx]
        def RW(self, j = [0]):
            data = R.read(self.length)
            if self.name == 'min':
                j[0] += 1
            if self.tp_name not in ('c'):
                unpacked = struct.unpack(f'<{self.tp_name}', data)[0]
            elif self.tp_name == 'c':
                unpacked = struct.unpack(f'<{self.tp_name}', data)[0].decode()
            output(self.name, unpacked, data)
            return unpacked
    return Basic

for i, j in enumerate(type_table):
    globals()[j] = CreateBasicClass(j)

class TP_SKIP:
    def __init__(self, d_length):
        self.tp_name = 'TP_SKIP'
        self.length = d_length
    def RW(self):
        R.read(self.length)

class TP_STRING:
    def __init__(self, name, d_length):
        self.name = name
        self.tp_name = 'TP_STRING'
        self.length = d_length
    def RW(self):
        data = R.read(1)
        if data.hex() == '00' and self.name == 'DATE':
            now = time.strftime('%Y-%m-%d %H:%M:%S')
            GLB['time'] = now
            output(self.name, now, b'\x00')
            R.read(self.length - 1)
        elif data.hex() == '00':
            output(self.name,'nan',b'\x00')
            R.read(self.length - 1)
        else:
            length_record = struct.unpack('B', data)[0]
            data2 = R.read(length_record)
            unpacked = struct.unpack(f'{length_record}s', data2)[0].decode()
            output(self.name, unpacked, data)
            R.read(self.length - 1 - length_record)
            return unpacked

class TP_BYTE48:
    def __init__(self, name):
        self.name = name
        self.length = 1
    def RW(self):
        data = R.read(1)
        bit0 = b2int(data) & 0b00000001
        if bit0 == 1:
            W.write(f'signed_or_absolute,signed\n')
            return 'signed'
        else:
            W.write('signed_or_absolute,absolute\n')
            return 'absolute'

class TP_MIN:
    def __init__(self, name):
        self.name = name
        self.length = 4
    def RW(self):
        data = R.read(3)
        data = R.read(1)
        if data.hex() == 'ff':
            W.write(f'min,{np.nan}\n')
            return np.nan
        else:
            R.seek(-4, 1)
            data = R.read(4)
            limit_tmp = struct.unpack('<f', data)[0]
            W.write(f'min,{limit_tmp}\n')
            return limit_tmp

class TP_MAX:
    def __init__(self, name):
        self.name = name
        self.length = 4
    def RW(self):
        data = R.read(3)
        data = R.read(1)
        if data.hex() == 'ff':
            W.write(f'max,{np.nan}\n')
            #R.read(3)
            return np.nan
        else:
            R.seek(-4, 1)
            data = R.read(4)
            limit_tmp = struct.unpack('<f', data)[0]
            W.write(f'max,{limit_tmp}\n')
            return limit_tmp

class TP_BYTE15:
    def __init__(self, name):
        self.name = name
        self.length = 1
    def RW(self):
        data = R.read(1)
        bit0 = b2int(data) & 0b00000001
        if bit0 == 0:
            GLB['TESTS'] = 125
        else:
            GLB['TESTS'] = 250


name1 = c('name1')
name2 = c('name2')
func = b('func')
min_ = TP_MIN('min')
max_ = TP_MAX('max')
skip = TP_SKIP(38)
byte15 = TP_BYTE15('byte15')
byte48 = TP_BYTE48('byte48')

if LIM_FIL.endswith('.gz'):
    R = gzip.open(LIM_FIL, 'rb')
else:
    R = open(LIM_FIL, 'rb')
R = io.BytesIO(R.read())
R.seek(14)
byte15.RW()

with open(LIM_OUT, 'w') as W:
    name1.RW()
    name2.RW()
    
    GLB['TEST'] = {}
    start = 96
    for i in range(GLB['TESTS']):
        R.seek(start, 0)
        W.write(f'TestNumber,{i + 1}\n')
        func.RW()
        GLB['TEST'][i + 1] = {}
        
        min_tmp = min_.RW()
        max_tmp = max_.RW()
        if (not np.isnan(min_tmp) and not np.isnan(max_tmp)):
            if (min_tmp < 0 and max_tmp < 0):
                if min_tmp > max_tmp:
                    min_tmp, max_tmp = max_tmp, min_tmp
            elif (min_tmp >= 0 and max_tmp <= 0 and abs(max_tmp) > min_tmp):  # 20190918 Rule 5
                min_tmp, max_tmp = max_tmp, min_tmp
            else:
                pass
        elif np.isnan(min_tmp) and (not np.isnan(max_tmp)) and max_tmp < 0:
            min_tmp, max_tmp = max_tmp, min_tmp
        elif (not np.isnan(min_tmp)) and np.isnan(max_tmp) and min_tmp < 0:
            min_tmp, max_tmp = max_tmp, min_tmp
        
        GLB['TEST'][i + 1]['min'] = min_tmp
        GLB['TEST'][i + 1]['max'] = max_tmp
        # GLB['TEST'][i+1]['signed_or_absolute'] = byte48.RW()
        start = start + 50
R.close()

#add limit to res file
#with open("debug_add_test_lim", "w") as dbf:
#    dbf.write("RES_FIL is {}\n".format(RES_FIL))
#    dbf.write("FIN_OUT is {}\n".format(FIN_OUT))
with open(RES_FIL, 'r') as R2:
    with open(FIN_OUT, 'w') as W2:
        flag_limit = 1
        for line in R2:
            line = line.strip()
            splited = line.split(',')
            if re.findall(r'^(#Test_name)', splited[0]):
                flag_limit *= -1
                if DS_FT_FLAG == 'FT':
                    W2.write('#Test_name,test_num,OTI_enabled,low_limit,high_limit,\n')
                else:
                    W2.write('#Test_name,test_num,low_limit,high_limit,\n')
                continue
            elif re.findall(r'^(program)', splited[0]):
                flag_limit *= -1
                if DS_FT_FLAG == 'DS':
                    W2.write(
                        'program,LOT_ID,WAFER_ID,testerType,failedTestNumber,SN,die_X,die_Y,softBinNumber,softBinName,RES\n')
                else:
                    W2.write('program,testerType,PRODUCT_ID,LOT_ID,failedTestNumber,SN,softBinNum,softBinName,RES\n')
                continue
            if flag_limit == 1:
                W2.write(f'{line}\n')
            else:
                W2.write(f'{",".join(splited)},')
                if DS_FT_FLAG == 'FT':
                    W2.write(f'{find_enabled_by_test_num(int(splited[1]))},')
                W2.write(f'{GLB["TEST"][int(splited[1])]["min"]},')
                W2.write(f'{GLB["TEST"][int(splited[1])]["max"]},\n')

old_basename = os.path.basename(FIN_OUT)
new_basename = '.'.join(old_basename.split('.')[:-2])
new_under_outDir = os.path.join(OUT_DIR, new_basename)
if not DEBUG:
    os.remove(LIM_OUT)
os.remove(RES_FIL)
if not os.path.exists(OUT_DIR):
    os.makedirs(OUT_DIR)
shutil.move(FIN_OUT, new_under_outDir)
